package server;
import java.io.IOException;
import java.util.ArrayList;

 /** Classe dedicata alla gestione della creazione degli utenti e delle loro rispettive speci.<br \>
 * La classe si occupa di controllare che gli utenti si registrino con dei nomi validi, nonche' non possano esistere due giocatori con stesso username. Inoltre, si controlla anche che uno stesso giocatore possa efettuare il login se lo ha gia' fatto da un altro terminale.<br \>
 * Riguardo alla creazione della razza, anche qui un utente ne puo' avere una e una sola, precisando che il suo nome sia anchìesso valido.<br \>
 * Ad ogni utente, in fase di login, viene assegnato un token che lo identifichera' per tutto il corso della sua connessione.
 */
public class Gioco{
	
	private ArrayList<Utente> utenti = new ArrayList<Utente>();
	private int numutenti = 1;
	private ArrayList<String> nomeSpeci = new ArrayList<String>();
	
	//@public invariant this.utenti.size() >= 0;
	//@public invariant this.numutenti >= 1;
	//@public invariant this.nomeSpeci.size() >= 0;
	
	/**
	 * Costruttore della classe. Si preoccupa principalmente di verificare se siano stati salvati i dati di un eventuale precedente partita (in caso il server sia caduto). Inizializza inoltre gli array che conterranno gli utenti e i nomi di tutte le speci create.
	 */
	@SuppressWarnings("unchecked")
	public Gioco(){
		try {
			this.utenti = (ArrayList<Utente>)(Salvataggio.carica("utenti"));
			this.nomeSpeci = (ArrayList<String>)(Salvataggio.carica("nomeSpeci"));
			System.out.println("Utenti caricati da file.");
		} catch (ClassNotFoundException e) {
			System.out.println("Errore caricamento da file. Classi non trovati.");
		} catch (IOException e) {
			System.out.println("File utenti.obj o nomeSpeci.obj non trovato, generato un nuovo arrayList vuoto.");
			this.utenti = new ArrayList<Utente>();
			this.nomeSpeci = new ArrayList<String>();
		}
	}
	
	//@requires !(nome.equals("") && pass.equals("") && nome.contains(" ") && pass.contains(" "));
	//@ensures (* if !(nome.equals("") && pass.equals("") && nome.contains(" ") && pass.contains(" ") *) ==> \return back[0] == true 
	//@ && back[1] == nome 
	//@ && back[2] == pass;
	/**
	 * Metodo invocato per la creazione di un utente. Il metodo controlla che il nome passatogli sia valido o che non esista un altro giocatore con lo stesso nome.
	 * @param nome Nome scelto dall'utente.
	 * @param pass Password inserita dall'utente.
	 * @return Torna un ArrayList di oggetti contenente true, il nome dell'utente e la password in caso l'operazione sia corretta.<br \>
	 * In caso contrario un ArrayList di oggetti contente false e un numero: 0 in caso di username gia' utilizzato, 99 per username o password non valido.
	 */
	public synchronized ArrayList<Object> creaUtente(String nome, String pass){
		Utente x = null;
		ArrayList<Object> back = new ArrayList<Object>();
		
		if( nome.equals("") || pass.equals("") || nome.contains(" ") || pass.contains(" ")){ //controllo validita' nome e password
			back.add(false);
			back.add(99);//nome o pass non valido
			return back;
		}
		x = controlloNomeGiocatore(nome); //controllo esistenza o meno del nome giocatore
		if ( x == null){
			int k = Mappa.SIZE_X;
			int z = Mappa.SIZE_Y;
			Utente ut = new Utente(nome, pass, k, z);
			utenti.add(ut);
			
			salvaUtenti();
			
			back.add(true);
			back.add(nome);
			back.add(pass);
			return back;
		}
		else{
			back.add(false);//usernameOccupato; username gia' utilizzato
			back.add(0);
			return back;
		}
	}
	
	/**
	 * Metodo chiamato per effettuare il login di un utente. Il metodo controlla che il giocatore si sia precedentemente registrato. In caso di corretto login, restituisce al client un identificatore token, diverso per ogni utente e valido fino alla disconnessione.
	 * @param nome Nome dell'utente con cui si e' registrato.
	 * @param pass Password scelta dall'utente al momento della registrazione.
	 * @return Ritorno di un ArrayList di oggetti contente true e il token dato all'utente.<br \>
	 * In caso di errore ritorna un ArrayList di oggetti contente false e 1 se l'autenticazione e' fallita. 
	 */
	public synchronized ArrayList<Object> login(String nome, String pass){
		Utente ut = null;
		ArrayList<Object> back = new ArrayList<Object>();
		
		ut = controlloNomeGiocatore(nome);
	
		if(ut != null) {
			if( ut.getToken() != -1 ){
				back.add(false);
				back.add(1); //autenticazione fallita
				return back;
			}
			if( ut.getPassword().equals(pass) ){
				ut.setToken(numutenti);
				back.add(true);
				back.add(numutenti);
				numutenti++;
				return back;
			}
			else{
				back.add(false);//autenticazioneFallita; nomeUtente non trovato
				back.add(1);
				return back;	
			}
		}
		else{
			back.add(false);//autenticazioneFallita; nomeUtente non trovato
			back.add(1);
			return back;
		}
	}
	
	/**
	 * Metodo usato per creare una razza. Viene controllata la validita' del nome scelto e che non sia gia' stato utilizzato per un'altra specie. In caso l'utente abbia gia' una razza, ritorna errore.
	 * @param tok Token associato all'utente che fa la richiesta.
	 * @param nome Nome della razza da creare.
	 * @param tipo Tipo della razza: "c" per carnivoro, "e" per erbivoro.
	 * @return In assenza di errori, un ArrayList di oggetti contenente true.<br \>
	 * In caso di errore, torna un ArrayList di oggetti con false e un numero: 98 in caso del nome razza non valido, 3 per indicare che il token associato all'utente ha un errore, 2 per nome razza occupato, 97 se l'utente ha gia' una razza.
	 */
	public synchronized ArrayList<Object> creaRazza(int tok,String nome, String tipo){
		boolean x = true;
		int i = 0;
		Utente ut;
		ArrayList<Object> back = new ArrayList<Object>();	
		if( nome.equals("") || nome.contains(" ")){
			back.add(false);
			back.add(98);//nomeRazzaNonValido
			return back;
		}
		if( nomeSpeci.indexOf(nome) != -1 ){
			back.add(false);
			back.add(2); //nome razza occupato
			return back;
		}
		ut = controlloToken(tok);
		if( ut == null){
			back.add(false);//tokenNonValido; token non trovato
			back.add(3);
			return back;
		}
		if( ut.getSpecie() != null){
				back.add(false);//specieGiaCreata
				back.add(97);
				return back;
		}
		else{
			if ( nomeSpeci.size() == 0){}
			else {
				do{
					if( nomeSpeci.get(i).equals(nome) )
						x = false;
					else i++;
				} while( i < nomeSpeci.size() && x == true);
			}
			if ( x == false ){
				back.add(false);//nomeRazzaOccupato
				back.add(2);
				return back;
			}
			else{
				nomeSpeci.add(nome);
				ut.creaSpecie(nome, tipo);
				back.add(true);
				return back;
			}
		}
	}
	
	/**
	 * Metodo invocato per richiedere la classifica della partita corrente. Vengono inviati i dati inerenti ai giocatori e alla loro specie che hanno partecipato ad almeno un turno della partita corrente.
	 * @param tok Token del giocatore che richiede la classifica.
	 * @return Torna un ArrayList di oggetti con true e tante celle quanti sono le speci che hanno partecipato alla partita.<br \>
	 * In caso di errore, un ArrayList di oggetti contente false con un numero, 3, indicante che il token del giocatore non e' valido.
	 */
	public ArrayList<Object> classifica(int tok){
		Utente ut = null;
		String[] lista = new String[4];
		int i = 0;
		ArrayList<Object> back = new ArrayList<Object>();
		
		ut = controlloToken(tok);
		if (ut == null){
			back.add(false);//tokenNonValido; token non trovato
			back.add(3);
			return back;
		}
		else{
			if(utenti.size() == 1 && utenti.get(0).getSpecieMemo().size() == 0){
				back.add(false);
				back.add(95);
				return back;
			}
			else{
			back.add(true);
				for( i = 0; i < utenti.size(); i++){
					for(int k = 0; k < utenti.get(i).getSpecieMemo().size(); k++){
						lista = new String[4];
						lista[0] = utenti.get(i).getUserID();
						lista[1] = utenti.get(i).getSpecieMemo().get(k).getNomeSpecie();
						lista[2] = String.valueOf(utenti.get(i).getSpecieMemo().get(k).getPunteggio());
						lista[3] = utenti.get(i).getSpecieMemo().get(k).getEstinta();
						back.add(lista);
					}
				}
			return back;
			}
		}
	}
	
	/**
	 * Metodo chiamato per effettuare il logout. In caso corretto, il token del giocatore viene settato a -1 per indicare che il giocatore, memorizzato nel server, non e' loggato.
	 * @param tok Token del giocatore che richiede di effettuare il logout.
	 * @return Un ArrayList di oggetti contenente true se l'operazione va a buon fine. In caso contrario, torna false con un numero, 3, indicante che il token del giocatore non e' valido.
	 */
	public synchronized ArrayList<Object> logout(int tok){
		Utente ut;
		ArrayList<Object> back = new ArrayList<Object>();
		
		ut = controlloToken(tok);
		if( ut == null){
			back.add(false);//tokenNonValido; token non trovato
			back.add(3);
			return back;
		}
		else{
			ut.setToken(-1);
			back.add(true);
			return back;
		}
	}

	/**
	 * Metodo privato che controlla che il nome del giocatore che cerca di registrarsi/effettuare il login, esista.
	 * @param nome
	 * @return Se esiste il nome, torna l'oggetto utente associato a quel nome precedenteente registrato, altirmenti torna null.
	 */
	private Utente controlloNomeGiocatore(String nome){

		boolean x = false;
		int i = 0;
		
		if(utenti.size() == 0)
			return null;
		do {
			if( utenti.get(i).getUserID().equals(nome) )
				x = true;
			else i++;
		} while ( i < utenti.size() && x != true);
		if( x == true )
			return utenti.get(i);
		else return null;
	}
	
	/**
	 * Metodo che controlla la validita' del token di chi chiede un azione.
	 * @param tok Token dell'utente che cerca di effettuare un'operazione.
	 * @return Torna l'oggetto utente corrispondente al token, se esiste, null altrimenti.
	 */
	public Utente controlloToken(int tok){
		
		Utente result = null;
		for(int i=0; i<utenti.size() && result==null; i++){
			if(utenti.get(i).getToken() == tok){
				result = utenti.get(i);
			}
		}
		
		return result;
	}
	
	/**
	 * Metodo che memorizza i dati della classe su file, per poterli eventualmente caricare in caso il server crashi.
	 * @return True se il salvataggio e' andato a buon fine. False in caso contrario.
	 */
	@SuppressWarnings("unchecked")
	public Boolean salvaUtenti(){
		try{
			
			Salvataggio.salva(utenti, "tmpUtenti");
			ArrayList<Utente> utentiDaSal = (ArrayList<Utente>) Salvataggio.carica("tmpUtenti");
			
			for(int i=0; i<utentiDaSal.size(); i++){
				utentiDaSal.get(i).setToken(-1);
			}
			
			Salvataggio.salva(utentiDaSal, "utenti");
			Salvataggio.elimina("tmpUtenti");
			return true;
			
		}catch (IOException e) {
			System.out.println("Gioco: Errore nel salvataggio.");
			return false;
		}catch (ClassNotFoundException e) {
			System.out.println("Gioco: Errore nel salvataggio.");
			return false;
		}
	}
	
	/**
	 * Metodo che salva l'ArrayList di stringhe contenente i nomi delle speci memorizzate fino adesso.
	 * @return True se l'operazione e' andata a buon fine, false in caso contrario.
	 */
	public Boolean salvaNomeSpeci(){
		try{
			
			Salvataggio.salva(nomeSpeci, "nomeSpeci");
			return true;
			
		}catch (IOException e) {
			System.out.println("Gioco: Errore nel salvataggio.");
			return false;
		}
	}
	
	/**
	 * Metodo che restituisce l'ArrayList Utenti.
	 * @return L'ArrayList di oggetti Utenti.
	 */
	public ArrayList<Utente> getUtenti(){
		return this.utenti;
	}
	
	/**
	 * Metodo che restituisce la variabile Numutenti della classe, indicante il numero di utenti fino a quel momento loggati.
	 * @return Un intero indicante il numero dei giocatori che hanno effettuato il login.
	 */
	public int getNumutenti(){
		return this. numutenti;
	}
	
	/**
	 * Metodo che restituisce l'ArrayList di stringhe contente i nomi di tutte le speci create fino ad allora.
	 * @return L'ArrayList di stringhe dei nomi delle speci.
	 */
	public ArrayList<String> getNomeSpeci(){
		return this.nomeSpeci;
	}
}